#ifndef _CMT_MONOLITHIC
/*! \file
        \brief        Contains the CMT Level 1 interface

        This level contains OS-dependent implementations of Serial and File 
        communication, as well as some other functionality like 'sleep'.

        \section FileCopyright Copyright Notice 
        Copyright (C) Xsens Technologies B.V., 2006.  All rights reserved.

        This source code is intended for use only by Xsens Technologies BV and
        those that have explicit written permission to use it from
        Xsens Technologies BV.

        THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
        KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
        PARTICULAR PURPOSE.
*/
#endif
#ifndef _CMT1_H_2006_04_12
#define _CMT1_H_2006_04_12

#ifndef _CMTDEF_H_2006_05_01
#        include "cmtdef.h"
#endif

#ifdef _WIN32
#        include <windows.h>
//#        include <sys/types.h>
#else
#        include <termios.h>
// these are not required by level 1, but to keep the higher levels platform-independent they are put here
#        include <stdlib.h>
#        include <string.h>
#        include <stddef.h>
#define _strnicmp        strncasecmp
#endif

#include <stdio.h>

//! The namespace of all Xsens software since 2006.
namespace xsens {

#ifndef CMTLOG
#        if defined(_DEBUG) || defined(_LOG_ALWAYS)
                void CMTLOG(const char *str, ...);
#                define CMTEXITLOG(str)        JanitorFunc2<const char*,XsensResultValue> _cmtExitLog(CMTLOG,str " returns %u",m_lastResult);
#        else
#                define CMTLOG(...)
#                define CMTEXITLOG(...)
#        endif
#endif

#ifndef _WIN32
int _wcsnicmp(const wchar_t* s1, const wchar_t* s2,int count);
#endif

//////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// Cmt1s  /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////

/*! \brief The low-level serial communication class.
*/
class Cmt1s {
private:
        //! This object cannot be copied, so this function is not implemented.
        Cmt1s(const Cmt1s& ref);

        #ifdef _LOG_RX_TX
                FILE* rx_log;
                FILE* tx_log;
        #endif

        /*! \brief The bytes received function.
        
                This function is automatically called every time binary data is read from the 
                connected COM port.
        */
        CmtCallbackFunction m_onBytesReceived;
        //! Custom, user supplied parameter for the OnBytesReceived callback function, passed as the first argument
        int32_t m_onBytesReceivedInstance;
        //! Custom, user supplied parameter for the OnBytesReceived callback function, passed as the last argument
        void* m_onBytesReceivedParam;
protected:
                //! The baudrate that was last set to be used by the port
        uint32_t m_baudrate;
                //! The time at which an operation will end in ms, used by several functions.
        uint32_t m_endTime;
                //! Indicates if the port is open or not
        bool m_isOpen;
                //! The last result of an operation
        mutable XsensResultValue m_lastResult;
                //! The opened COM port nr
        uint16_t m_port;
        char m_portname[32];
        /*! The default timeout value to use during blocking operations.
                A value of 0 means that all operations become non-blocking.
        */
        uint32_t m_timeout;

        #ifdef _WIN32
                DCB                m_commState;                //!< Stored settings about the serial port
                HANDLE        m_handle;                        //!< The serial port handle
        #else
                termios        m_commState;                //!< Stored settings about the serial port
                int32_t                m_handle;                        //!< The serial port handle
                typedef int32_t HANDLE;
        #endif
public:
                //! Default constructor, initializes all members to their default values.
        Cmt1s();
                //! Destructor, de-initializes, frees memory allocated for buffers, etc.
        ~Cmt1s();
                //! \brief Close the serial communication port.
        XsensResultValue close (void);
        /*! \brief Manipulate the Serial control lines

                The function manipulates the serial control lines that are indicated by the 
                mask parameter. Note that only the DTR and RTS lines can be set by win32.
                \param mask                Indicates which lines are to be manipulated and which should be
                                                left alone.
                \param state        Contains the new state of the control lines.
        */
        XsensResultValue escape (const CmtControlLine mask, const CmtControlLine state);
        /*! \brief Flush all data to be transmitted / received.
        
                This function tries to send and receive any remaining data immediately 
                and does not return until the buffers are empty.
        */
        XsensResultValue flushData (void);
                //! Return the baudrate that is currently being used by the port
        uint32_t getBaudrate(void) const { return m_baudrate; }
                //! Return the handle of the port
        HANDLE getHandle(void) const { return m_handle; }
                //! Retrieve the port number that was last successfully opened.
        uint16_t getPortNr (void) const { return m_port; }
                //! Retrieve the port name that was last successfully opened.
        void getPortName(char *portname) const { sprintf(portname, "%s", m_portname); }
                //! Return the error code of the last operation.
        XsensResultValue getLastResult(void) const { return m_lastResult; }
                //! Return the current timeout value
        uint32_t getTimeout (void) const { return m_timeout; }
                //! Return whether the communication port is open or not.
        bool isOpen (void) const { return m_isOpen; }
        
        /*! \brief Open a communcation channel to the given serial port name.
        
                The port is automatically initialized to the given baudrate.
                If the baudrate is set to 0, the baud rate is automatically detected. If possible.
        */
        XsensResultValue open ( const char *portName,
                                                        const uint32_t baudRate = CMT_DEFAULT_BAUD_RATE,
                                                        uint32_t readBufSize = CMT_DEFAULT_READ_BUFFER_SIZE,
                                                        uint32_t writeBufSize = CMT_DEFAULT_WRITE_BUFFER_SIZE);

#ifdef _WIN32
        /*! \brief Open a communication channel to the given COM port number.
        
                The port is automatically initialized to the given baud rate.
                If the baudrate is set to 0, the baud rate is automatically detected. If possible.
        */
        XsensResultValue open ( const uint32_t portNumber,
                                                        const uint32_t baudRate = CMT_DEFAULT_BAUD_RATE,
                                                        uint32_t readBufSize = CMT_DEFAULT_READ_BUFFER_SIZE,
                                                        uint32_t writeBufSize = CMT_DEFAULT_WRITE_BUFFER_SIZE);
#endif
        /*! \brief Read data from the serial port and put it into the data buffer.

                This function reads as much data as possible from the com port (non-blocking) and 
                put as much data as will fit into the data buffer. Any excess data is stored in 
                the \c m_readBuffer member variable. If there was enough data in m_readBuffer to 
                fulfill the request, the data parameter is first filled and the port is polled 
                afterwards.
                \param maxLength        The maximum amount of data read.
                \param data                        Pointer to a buffer that will store the received data.
                \param length                The number of bytes placed into \c data.
        */
        XsensResultValue readData (const uint32_t maxLength, uint8_t* data,
                                                                uint32_t* length = NULL);
        //! Set the callback function for when bytes have been received
        XsensResultValue setCallbackFunction(CmtCallbackType tp, int32_t instance, CmtCallbackFunction func, void* param);
        /*! \brief Set the default timeout value to use in blocking operations.

                This function sets the value of m_timeout. There is no infinity value. The value 0 
                means that all blocking operations now become polling (non-blocking) operations. 
                If the value is set to or from 0, the low-level serial port settings may be 
                changed in addition to the m_timeout value.
        */
        XsensResultValue setTimeout (const uint32_t ms = CMT1_DEFAULT_TIMEOUT);
        /*! \brief Wait for data to arrive or a timeout to occur.

                The function waits until \c maxLength data is available or until a timeout occurs.
                The function returns success if data is available or XsensResultValue::TIMEOUT if a 
                timeout occurred. A timeout value of 0 indicates that the default timeout stored
                in the class should be used.
        */
        XsensResultValue waitForData (const uint32_t maxLength, uint8_t* data,
                                                                uint32_t* length = NULL);
        /*! \brief Write the data to the serial port.

                The function writes the given data to the connected COM port.
                The default timeout is respected in this operation.
        */
        XsensResultValue writeData (const uint32_t length, const uint8_t* data,
                                                                uint32_t* written);

};

//////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// Cmt1f  /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////

/*! \brief The low-level file communication class.
*/
class Cmt1f {
private:
                //! This object cannot be copied, so this function is not implemented.
        Cmt1f(const Cmt1f& ref);

protected:
                //! The file handle
        FILE* m_handle;
                //! Contains the size of the file
        CmtFilePos m_fileSize;
                //! The last read position in the file
        CmtFilePos m_readPos;
                //! The last write position in the file
        CmtFilePos m_writePos;
                //! The last result of an operation
        mutable XsensResultValue m_lastResult;
                //! Contains the name of the file that was last successfully opened.
        char m_filename[CMT_MAX_FILENAME_LENGTH];
                //! Contains the name of the file that was last successfully opened using unicode.
        wchar_t m_filename_w[CMT_MAX_FILENAME_LENGTH];
                //! Indicates if the file is open or not.
        bool m_isOpen;
                //! Indicates if we're using the unicode filename or the regular filename
        bool m_unicode;
        /*! \brief Indicates whether the last operation was a read or write operation.

                This value is used to check whether or not a seek is required to perform a 
                requested read or write operation.
        */
        bool m_reading;
                //! Indicates if the file was opened in read-only mode
        bool m_readOnly;
                //! Change from writing to reading mode
        void gotoRead(void);
                //! Change from reading to writing mode
        void gotoWrite(void);
public:
                //! Default constructor, initializes all members to their default values.
        Cmt1f();
                //! Destructor.
        ~Cmt1f();
        /*! \brief Write data to the end of the file.

                The function writes the given data to the file at the end. The current write
                position is also moved to the end of the file.
        */
        XsensResultValue appendData(const uint32_t length,  const void* data);
                //! Close the file.
        XsensResultValue close(void);
                //! Close the file and delete it.
        XsensResultValue closeAndDelete(void);
                //! Open an empty file.
        XsensResultValue create(const char* filename);
                //! Open an empty file using a unicode path + filename.
        XsensResultValue create(const wchar_t* filename);
        /*! \brief Delete the given data from the file.
        
                The function erases the given data from the file at the given write position. This
                operation may take a while to complete, but is faster than insertData.

                The write position is not changed and the read position        is checked for validity.
        */
        XsensResultValue deleteData(const CmtFilePos start, const uint32_t length);
        /*! \brief Find a string of bytes in the file

                The function searches from the current read position until the given \c needle is 
                found. If the needle is not found, XsensResultValue::NOT_FOUND is returned. The function
                will update the seek position to the first character of the found needle.
                \param needle                The byte string to find.
                \param needleLength        The length of the byte string.
                \param pos                        Out: The position where the needle was found. This will point
                                                        to the first character of the found needle.
        */
        XsensResultValue find(const void* needle, const uint32_t needleLength, CmtFilePos& pos);
        /*! \brief Flush all data to be written.
                This function writes any remaining data immediately and does not return
                until this is done.
        */
        XsensResultValue flushData(void);
                //! Return the size of the file.
        CmtFilePos getFileSize(void) const { return m_fileSize; }
                //! Return the result code of the last operation.
        XsensResultValue getLastResult(void) const        { return m_lastResult; }
        /*! \brief Retrieve the filename that was last successfully opened.

                \param filename        A buffer for storing the filename. The buffer should be able 
                                to hold the filename. A safe size is to make it at least 256 bytes.
        */
        XsensResultValue getName(char* filename) const;
        /*! \brief Retrieve the filename that was last successfully opened in unicode.

                \param filename        A buffer for storing the filename. The buffer should be able 
                                to hold the filename. A safe size is to make it at least 256 wide characters.
        */
        XsensResultValue getName(wchar_t* filename) const;
                //! Return the current read position.
        CmtFilePos getReadPos(void) const { return m_readPos; }
                //! Return the current write position.
        CmtFilePos getWritePos(void) const { return m_writePos; }
        /*! \brief Insert the given data into the file.
        
                The function writes the given data to the file at the current write position. This
                operation may take a while to complete.
                
                The write position is placed at the end of the inserted data.
        */
        XsensResultValue insertData(const CmtFilePos start, const uint32_t length,
                                                                const void* data);
                //! Return whether the file is open or not.
        bool isOpen(void) const { return m_isOpen; }
                //! Return whether the file is readonly or not.
        bool isReadOnly(void) const { return !m_isOpen || m_readOnly; }
                //! Open a file.
        XsensResultValue open(const char* filename, const bool create, const bool readOnly);
                //! Open a file using a unicode filename.
        XsensResultValue open(const wchar_t* filename, const bool create, const bool readOnly);
        /*! \brief Read data from the file and put it into the data buffer.

                This function reads exactly the number of bytes as requested from the file.
                \param maxLength        The amount of data that will be read.
                \param data                        Pointer to a buffer that will store the read data.
                \param length                pointer to a variable that will store the number of bytes
                                                        that were actually read. The parameter may be NULL.
        */
        XsensResultValue readData(const uint32_t maxLength, void* data, uint32_t* length);
        /*! \brief Read data from the file and put it into the data buffer.

                This function reads upp to the number of bytes as requested from the file.
                The function will also stop if the given terminator character is encountered.
                The terminator is included in the output buffer.
                \param maxLength        The amount of data that will be read.
                \param data                        Pointer to a buffer that will store the read data.
                \param terminator        A character that will end the read operation if encountered.
                \param length                The actual number of bytes read, including the terminator
                                                        character, if encountered.
        */
        XsensResultValue readData(const uint32_t maxLength, const char terminator, void* data, uint32_t* length);
        /*! \brief Set the new absolute read position

                The read position is checked against the filesize before committing.
        */
        XsensResultValue setReadPos(const CmtFilePos pos);
        /*! \brief Set the new absolute write position

                The write position is checked against the filesize before committing.
        */
        XsensResultValue setWritePos(const CmtFilePos pos = -1);

        /*! \brief Write data to the file.

                The function writes the given data to the file at the current write position.
        */
        XsensResultValue writeData(const uint32_t length,  const void* data);
};

}        // end of xsens namespace

#endif        // _CMT1_H_2006_04_12
